package com.nx.platform.es.bean.modle.query;

import com.google.common.base.Enums;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ListMultimap;
import com.google.common.primitives.Floats;
import com.nx.platform.es.bean.dto.RequestContext;
import com.nx.platform.es.biz.wrapper.parser.StatementOperator;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.elasticsearch.common.Strings;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.MultiMatchQueryBuilder.Type;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author
 * @since 2016年10月13日
 */
public class StringMultiMatchShouldHandler implements QueryFieldHandler {

    @Override
    public void handle(ImmutableMap<String, ?> fieldConfig, RequestContext context, String fieldFace,
                       StatementOperator operator, String fieldValue, ListMultimap<Boolean, QueryBuilder> queryBuilders) {
        //
        if (Strings.isNullOrEmpty(fieldValue)) {
            return;
        }
        // 记录Query信息(Trace)
        context.getTrace().append("&").append(fieldFace).append(operator.getSymbol()).append(fieldValue);
        //
        String face = MapUtils.getString(fieldConfig, "face");
        Object _fields = MapUtils.getObject(fieldConfig, "_fields");
        String _type = MapUtils.getString(fieldConfig, "_type", Type.BEST_FIELDS.toString());
        float _tie_breaker = MapUtils.getFloatValue(fieldConfig, "_tie_breaker", 0f);
        int alternatives = MapUtils.getIntValue(fieldConfig, "_alternatives", 8);

        Preconditions.checkState(_fields instanceof List, "param _fields incorrect");
        List<?> fields = List.class.cast(_fields);
        String[] fieldNames = fields.stream().filter(Objects::nonNull).map(String::valueOf).toArray(String[]::new);
        if (fieldNames.length == 0) {
            return;
        }

        //
        List<String> words = Splitter.on("|").omitEmptyStrings().splitToList(fieldValue);
        if (words.isEmpty()) {
            return;
        }
        if (words.size() > alternatives) {
            throw new IllegalArgumentException("too more alternatives: field=" + face + ", value=" + fieldValue);
        }
        queryBuilders.putAll(operator.isSign(), words.stream()
                .map(word -> build(fieldNames, _type, _tie_breaker, word))
                .collect(Collectors.toList()));
    }

    private MultiMatchQueryBuilder build(String[] fieldNames, String _type, float _tie_breaker, String fieldValue) {
        Pair<String, Float> valueBoost = stringAndBoost(fieldValue, 1.0f);
        MultiMatchQueryBuilder multiMatch = QueryBuilders.multiMatchQuery(valueBoost.getKey());
        for (String fieldName : fieldNames) {
            Pair<String, Float> fieldBoost = stringAndBoost(fieldName, 1.0f);
            multiMatch.field(fieldBoost.getKey(), (float) (fieldBoost.getValue().doubleValue() * valueBoost.getValue().doubleValue()));
        }
        multiMatch.type(Enums.getIfPresent(Type.class, _type.toUpperCase()).or(Type.BEST_FIELDS));
        multiMatch.operator(Operator.AND);
        if (_tie_breaker > 0f && _tie_breaker <= 1.0f) {
            multiMatch.tieBreaker(_tie_breaker);
        }
        return multiMatch;
    }

    private Pair<String, Float> stringAndBoost(String origin, Float defaultBoost) {
        int index = origin.indexOf('^');
        if (index > 0) {
            String str = origin.substring(0, index);
            Float boost = Floats.tryParse(origin.substring(index + 1));
            return Pair.of(str, boost != null && boost > 0 ? boost : defaultBoost);
        } else {
            return Pair.of(origin, defaultBoost);
        }
    }

}
